#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2011 BidiX (http://bidix.info)
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
import logging
import mimetypes
import cgi
import re
import time, datetime
import sys

import config

#from google.appengine.ext import webapp
import webapp2
from google.appengine.ext.webapp import util
from google.appengine.api import users
from notewiki.model import Article, Role, area_for, extension, name_without_extension, user_role
from notewiki.model import convert_unicode_keywords
from notewiki.ui import template, format_user, text_to_html, render_user
from notewiki.ui import pages
import notewiki.ui
from operator import itemgetter

class StaticPageHandler(webapp2.RequestHandler):
	"""
		handler for static resources
		resources are file located in 'static_root_path'
	"""
	expires = 60*60*24*15 # 15 days max-age (release period)

	def get(self, *groups):
		if self.request.path == '/favicon.ico':
			file_path = '/images/favicon.ico'
		else:
			file_path = self.request.path
		static_root_path = self.app.config.get('static_root_path')
		type = mimetypes.guess_type(file_path)
		try:
			f = open(static_root_path+file_path, 'r')
			self.response.headers['Content-Type'] = '%s'%type[0]
			self.response.headers['Cache-Control'] = 'max-age=%s'%self.expires
			self.response.out.write(f.read())
		except IOError:
			logging.error("SaticPage not found : %s"%file_path)
			self.response.set_status(404)
			self.response.out.write(template.render("404", {"url": file_path}))
			
class ControledRequestHandler(webapp2.RequestHandler):
	"""
		Base class for handlers that control user access
	"""
	def check_access(self, url, write_access = False):
		self.user = users.get_current_user()
		return True

	def check_admin_access(self, url):
		self.check_access(url)
		if users.is_current_user_admin():
			return True
		else:
			self.response.set_status(401)
			pages.ForbiddenPage(self.response, {'url': url})
			return False

			
class ArticleHandler(ControledRequestHandler):
	"""
		handler for Articles and Folders
	"""
	file_expires = 60*60*24*1 # 1 day max-age for file

	def check_access(self, url, write_access = False):
		"""
			for all request, initialize variables used at access level
		"""
		self.user = users.get_current_user()
		self.url = url
		self.name = name_without_extension(self.url)
		self.name_extension = extension(self.url)
		self.command = self.request.query_string
		if self.name and (self.name[len(self.name)-1] == '/'):
			self.name = self.name[0:len(self.name)-1]
			self.command = 'list'
		self.area = area_for(self.name)
		self.user_role = user_role(self.name)
		if (self.user_role == Role.forbidden) or (write_access and (self.user_role == Role.reader)):
			self.response.set_status(401)
			pages.ForbiddenPage(self.response, {'url': self.name})
			return False
		else:
			self.context = {
				'app_name': self.app.config.get('wiki_app_name'),
				'path': self.request.path_info,
				'command': self.command,
				'user': format_user(self.request.url, self.app.config.get('wiki_base_url')),
				'username': render_user(self.user),
				'role': self.user_role,
				'msg' : ''
					}
			return True;
		
	def display_file(self, text, filename):
		self.response.headers['Content-Type'] = mimetypes.guess_type(filename)[0]
		self.response.headers['Cache-Control'] = 'max-age=%s'%self.file_expires
		self.response.out.write(text)

	def display_article(self, name, extension=''):
		article = Article.get_by_key_name(name)
		if not article and (name == 'home'):
			article = Article(name='home', text='Article "home" not found !')
		if article:
			if extension:
				logging.debug("Display %s as %s"%(name, extension))
				if extension == 'xml':
					text = article.to_xml()
				elif extension == 'html':
					text = text_to_html(article.text, article, self.app.config.get('wiki_base_url'))
				else:
					text = article.text
				self.display_file(text, self.url)
			else:
				logging.info("Display Article %s"%name)
				self.context.update(article.to_context())
				if '_file_' in article.tags:
					self.context['presentation_type'] = 'plain'
					self.context['presentation'] = article.text
					self.context['article_list'] = self.get_visible_article_list(article)
				else:
					self.context['presentation_type'] = 'html'
					self.context['presentation'] = text_to_html(article.text, article, self.app.config.get('wiki_base_url'))
					self.context['article_list'] = self.get_visible_article_list(article)
				pages.ArticlePage(self.response, self.context)
		else:
			logging.debug("Article %s not found."%name)
			self.response.set_status(404)
			pages.ArticleNotFoundPage(self.response, {'name': name})
			
	def display_form(self, article, context={}):
		article_context = article.to_context()
		article_context['title'] = cgi.escape(article_context['title'], True) 
		article_context['text'] = cgi.escape(article_context['text'], True) 
		self.context.update(article_context)
		self.context.update(context)
		pages.ArticleFormPage(self.response, self.context)		

	def display_move_form(self, article, context={}):
		self.context.update(article.to_context())
		self.context.update(context)
		pages.ArticleMovePage(self.response, self.context)		
		
	def edit_article(self, name):
		article = Article.get_by_key_name(name)
		if article:
			logging.info("Edit Article %s"%name)
			self.context['command'] = 'edit'
			self.context['timestamp'] = time.time()
			self.display_form(article)
		else:
			logging.debug("Article %s not found."%name)
			self.response.set_status(404)
			pages.NotFoundPage(self.response,  {'url': name})
		
	def move_article(self, name):
		article = Article.get_by_key_name(name)
		if article:
			logging.info("Move Article %s"%name)
			self.context['command'] = 'move'
			self.context['timestamp'] = time.time()
			self.display_move_form(article)
		else:
			logging.debug("Article %s not found."%name)
			self.response.set_status(404)
			pages.NotFoundPage(self.response,  {'url': name})

	def new_article(self):
		article = Article(key_name=self.name)
		logging.info("New Article %s"%self.name)
		self.context['command'] = 'new'
		self.display_form(article)

	def get_visible_article_list(self, article):
		return Article.list_all_visible(self.user, article.name)	
		
	def list(self):
		if not self.name:
			# a name is mandatory
			# article is used only as context for template
			self.name = ' '
		article = Article(key_name=self.name)
		visible_list = self.get_visible_article_list(article)
		self.context.update(article.to_context())
		self.context.update({
							'article_list': visible_list,
							})
		logging.info("Display list %s"%self.name)
		pages.ArticleListPage( self.response, self.context)

		
	def search(self):
		exp = self.request.params['exp']
		match_list = Article.list_with_regex(self.user, exp)
		# a name is mandatory
		# article is used only as context for template
		#article = Article(key_name=self.name)		
		#self.context.update(article.to_context())
		self.context.update({
							'title': 'search:%s'%exp,
							'article_list': match_list,
							})
		logging.info("Display match list for %s"%exp)
		pages.ArticleListPage( self.response, self.context)

	def delete_article(self):
		article = Article(key_name=self.name)
		if article:
			logging.info("Delete Article %s"%self.name)
			article.delete()
			if article.folder:
				self.redirect(self.app.config.get('wiki_base_url')+article.folder+'/')
			else:
				self.redirect(self.app.config.get('wiki_base_url'))
		else:
			logging.debug("Article %s not found."%self.name)
			self.response.set_status(404)
			pages.NotFoundPage(self.response, {'root_url': '', 'url': self.name})

	def article_from_form(self):
		params = self.request.params
		return Article(key_name=params['name'], name=params['name'], title=notewiki.ui.unescape(params['title']), text=notewiki.ui.unescape(params['text']), tag=params['tag'])
	
	def update_article(self):
		params = self.request.params
		key_name = None
		kwds = {}
		for (name, value) in params.items():
			if name == 'name':
				key_name = value
			kwds[name]=value
		form_article = self.article_from_form()
		article = Article.get_by_key_name(kwds['name'])
		if kwds.has_key('command') and (kwds['command'] == 'new'):
			article = Article.get_by_key_name(kwds['name'])
			if article:
				msg = "You try to create an existing article: %s"%kwds['name']
				logging.debug(msg)
				self.display_form(form_article, {'command' : kwds['command'], 'msg': msg})
				return
		if not re.match(r'^([a-z0-9\-_][a-z0-9\-_/]*)?[a-z0-9\-_]$', kwds['name']):
			msg = "Name is invalid. Name can contain only 'a-z0-9-_/' and no '/' in first and last position."
			logging.debug(msg)
			self.display_form(form_article, {'command' : kwds['command'], 'msg': msg})
			return
		if not re.match(r'^(( *[a-z0-9\-_]+ *.* )* *([a-z0-9\-_]+) *)?$', kwds['tag']):
			msg = "Tags are invalids. <br>A tag can contain only 'a-z0-9_'. Tags must be separated by \",\"."
			logging.debug(msg)
			self.display_form(form_article, {'command' : kwds['command'], 'msg': msg})
			return
		if article and kwds.has_key('timestamp') and (article.modified > datetime.datetime.fromtimestamp(float(kwds['timestamp']))):
			msg = "Article stored is newer than form's one. You should review and merge your change with stored version"
			logging.debug(msg)
			self.display_form(form_article, {'command' : kwds['command'], 'msg': msg})
			return
		logging.info("create/update article %s"%self.name)
		article = Article.add_or_replace(**convert_unicode_keywords(kwds))
		if self.name == kwds['name']:
			self.display_article(self.name, self.name_extension)
		else:
			self.redirect(self.app.config.get('wiki_base_url')+kwds['name'])

	def rename_article(self):
		params = self.request.params
		old_name = params['name']
		new_name = params['new_name']
		article = Article.get_by_key_name(old_name)
		if not re.match(r'^([a-z0-9\-_][a-z0-9\-_/]*)?[a-z0-9\-_]$', new_name):
			msg = "New Name is invalid. Name can contain only 'a-z0-9-_/' and no '/' in first and last position."
			logging.debug(msg)
			self.display_move_form(article, {'new_name' : new_name, 'msg': msg})
			return
		new_article = Article.get_by_key_name(new_name)
		if new_article:
			msg = "You try to rename to an existing article: %s"%new_name
			logging.debug(msg)
			self.display_move_form(article, {'new_name' : new_name, 'msg': msg})
			return
		logging.info("rename article %s"%self.name)
		new_article = article.rename(new_name)
		self.redirect(self.app.config.get('wiki_base_url')+new_name)
		
	def get(self, url):
		"""
			command : new | [display] | update | delete | edit
		"""
		if self.check_access(url, write_access = False):
			if self.command == 'list':
				self.list()
			elif not self.name:	
				self.name = 'home'
			if (self.command == '') or (self.command == 'display'):
				self.display_article(self.name, self.name_extension)
			elif self.command == 'edit':
				if self.user_role < Role.contributor:
					self.response.set_status(401)
					pages.ForbiddenPage(self.response, {'url': self.name})
					return	
				self.edit_article(self.name)			
			elif self.command == 'new':
				if self.user_role < Role.contributor:
					self.response.set_status(401)
					pages.ForbiddenPage(self.response, {'url': self.name})
					return
				self.new_article()	
			elif self.command == 'delete':
				if self.user_role < Role.contributor:
					self.response.set_status(401)
					pages.ForbiddenPage(self.response, {'url': self.name})
					return
				self.delete_article()				
			elif self.command == 'move':
				if self.user_role < Role.contributor:
					self.response.set_status(401)
					pages.ForbiddenPage(self.response, {'url': self.name})
					return	
				self.move_article(self.name)			

			
	def post(self, url):
		"""
		"""
		if self.check_access(url, write_access = True):
			if self.command == 'search':
				self.search()
			elif self.command == 'move':
				rename_user_role = user_role(self.request.params['new_name'])
				if rename_user_role > Role.reader: 
					self.rename_article()
				else:
					self.response.set_status(401)
					pages.ForbiddenPage(self.response, {'url': self.request.params['new_name']})
			else:
				self.update_article()
			
class TagHandler(ControledRequestHandler):	
	"""
		handler for tags
	"""
	
	def get(self, tag):
		if self.check_access(self.request.path_info):
			self.tag = tag
			article_list = Article.list_with_tag(self.tag)
			visible_list = []
			for a in article_list:
				role = user_role(a.name)
				if role >= Role.reader:
					visible_list.append(a)
			self.context = {
				'app_name':  self.app.config.get('wiki_app_name'),
				'path': self.request.path_info,
				'command': 'tag',
				'user': format_user(self.request.url, self.app.config.get('wiki_base_url')),
				'name': 'tags/'+self.tag,
				'title': 'tags/'+self.tag,
				'msg' : ''
						}
			self.context.update({
								'folders': None,
								'article_list': visible_list,
								})
			logging.info("Display list for tag %s"%self.tag)
			pages.ArticleListPage( self.response, self.context)

class TagListHandler(ControledRequestHandler):	
	"""
		handler for tag list
	"""
	
	def get(self):
		if self.check_access(self.request.path_info):
			tags = Article.get_all_tags(self.user)
			# sorted by tag in alphabetic order
			#sorted_tags = sorted(tags.items(), key=itemgetter(0), reverse=False)
			# sorted by use
			sorted_tags = sorted(tags.items(), key=itemgetter(1), reverse=True)
			self.context = {
				'app_name':  self.app.config.get('wiki_app_name'), 
				'user': format_user(self.request.url, self.app.config.get('wiki_base_url')),
				'username': render_user(self.user),
				'title': 'tags',
				'tags': sorted_tags,
							}
			logging.info("Display tag list")
			pages.TagListPage( self.response, self.context)

class XmlExport(ControledRequestHandler):
	'''From http://stackoverflow.com/questions/426820
	'''
	def get(self):
		if self.check_admin_access(self.request.path_info):
			xml = Article.all_to_xml()
			self.response.headers['Content-Type']='text/xml; charset=utf-8'
			self.response.headers['Cache-Control'] = 'no-cache'
			self.response.out.write(xml)
			logging.info("XMLExport - file exported")

class XmlImport(ControledRequestHandler):
	'''From http://stackoverflow.com/questions/426820
	'''
	def get(self):
		if self.check_admin_access(self.request.path_info):		
			pages.ImportPage(self.response, {'app_name':  self.app.config.get('wiki_app_name'), 'title': 'import', 'path': self.request.path_info})
		
	def post(self):
		if self.check_admin_access(self.request.path_info):		
			xml = self.request.get('file')
			Article.set_from_xml(xml)
			msg = "file imported successfully"
			logging.warn("XMLImport - file imported successfully")
			pages.ImportPage(self.response, {'app_name':  self.app.config.get('wiki_app_name'), 'title': 'import', 'path': self.request.path_info, 'msg': msg})
		

def notewikiapp(debug, config):
	return webapp2.WSGIApplication([
								('/favicon\.ico', StaticPageHandler),
								('((/css)|(/images)|(/js))(.*)', StaticPageHandler),
								('/tags/*', TagListHandler),
								('/tags/(.*)', TagHandler),
								('/_admin/export', XmlExport),
								('/_admin/import', XmlImport),
								('/(.*)', ArticleHandler),
								('.*', StaticPageHandler)],
                             debug=debug,
                             config=config)
